#include <Windows.h>
#include <stdio.h>
#include <conio.h>
#include <fstream>
#include <time.h>

#define BLOCK_CHAR 254
#define DEFAULT_COLOR 16
#define WELL_HEIGHT 20
#define WELL_WIDTH 10
#define ROTATED_CW 1
#define ROTATED_CCW 2

#include "ActionTimer.h"

HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

void MoveCursor(int a_X, int a_Y)
{
	COORD point = {a_X, a_Y};
	SetConsoleCursorPosition(hConsole, point);
}

void ColorCursor(int a_Color)
{
	SetConsoleTextAttribute(hConsole, a_Color);
}

void HideCursor(void)
{
	CONSOLE_CURSOR_INFO CCI;
	CCI.bVisible = false;
	CCI.dwSize = 100;
	::SetConsoleCursorInfo(hConsole, &CCI);
}

struct WellBlock
{
	int m_State;
	int m_Color;
	WellBlock():m_State(0), m_Color(DEFAULT_COLOR){}
	~WellBlock(){}
};

struct Conglomerate
{
	int m_X, m_Y;

	int m_Width;
	int m_Height;
	int m_Color;
	char** m_Map;

	void Copy(Conglomerate& a_Piece)
	{
		m_X = a_Piece.m_X;
		m_Y = a_Piece.m_Y;
		if(m_Map)
		{
			for(int y = 0; y < m_Height; ++y)
				delete [] m_Map[y];
			delete [] m_Map;
			m_Map = 0;
		}
		m_Width = a_Piece.m_Width;
		m_Height = a_Piece.m_Height;
		m_Map = new char* [m_Height];
		for(int y=0; y<m_Height;++y)
			m_Map[y] = new char [m_Width];
		m_Color = a_Piece.m_Color;
		for(int y = 0; y < m_Height; ++y)
			for(int x = 0; x < m_Width; ++x)
				m_Map[y][x] = a_Piece.m_Map[y][x];
	}


	Conglomerate()
	{
		m_Width = 0;
		m_Height = 0;
		m_Map = 0; 
		m_Color = 0;
		m_X = 0;
		m_Y = 0;
	}

	bool CreatePiece(void)
	{
		m_X=4; m_Y=0;
		std::ifstream INFILE;
		switch(rand()%7)
		{
		case 0:
			INFILE.open("BLOCKO.txt"); break;
		case 1:
			INFILE.open("BLOCKI.txt"); break;
		case 2:
			INFILE.open("BLOCKT.txt"); break;
		case 3:
			INFILE.open("BLOCKS.txt"); break;
		case 4:
			INFILE.open("BLOCKZ.txt"); break;
		case 5:
			INFILE.open("BLOCKL.txt"); break;
		case 6:
			INFILE.open("BLOCKJ.txt"); break;
		default:
			return false;
		}
		if(!INFILE.good())
			return false;

		if(m_Map)
		{
			for(int y = 0; y < m_Height; ++y)
				delete [] m_Map[y];
			delete [] m_Map;
			m_Map = 0;
		}

		INFILE >> m_Width;
		INFILE >> m_Height;
		INFILE >> m_Color;

		m_Map = new char* [m_Height];
		for(int y = 0; y < m_Height; ++y)
			m_Map[y] = new char [m_Width];

		int inputChar = 0;
		for(int y = 0; y < m_Height; ++y)
		{
			for(int x = 0; x < m_Width; ++x)
			{
				inputChar = INFILE.get();
				if(inputChar == '\n' || inputChar == '\r')
					--x;
				else
					m_Map[y][x] = inputChar;
			}
		}
		INFILE.close();
		return true;
	}
	void RotateCCW(void)
	{
		char** m_RotatedMap = new char* [m_Width];

		for(int i = 0; i < m_Width; ++i)
			m_RotatedMap[i] = new char [m_Height];

		for(int x = 0; x < m_Width; ++x)
			for(int y = 0; y < m_Height; ++y)
				m_RotatedMap[x][y] = m_Map[y][(m_Width-1)-x];

		for(int y = 0; y < m_Height; ++y)
			delete [] m_Map[y];
		delete [] m_Map;

		int temp = m_Height;
		m_Height = m_Width;
		m_Width = temp;

		m_Map = m_RotatedMap;
	}
	void RotateCW(void)
	{
		char** m_RotatedMap = new char* [m_Width];

		for(int i = 0; i < m_Width; ++i)
			m_RotatedMap[i] = new char [m_Height];

		for(int x = 0; x < m_Width; ++x)
			for(int y = 0; y < m_Height; ++y)
				m_RotatedMap[x][y] = m_Map[(m_Height-1)-y][x];

		for(int y = 0; y < m_Height; ++y)
			delete [] m_Map[y];
		delete [] m_Map;

		int temp = m_Height;
		m_Height = m_Width;
		m_Width = temp;

		m_Map = m_RotatedMap;
	}
	void Release(void)
	{
		if(m_Map)
		{
			for(int y = 0; y < m_Height; ++y)
				delete [] m_Map[y];
			delete [] m_Map;
			m_Map = 0;
		}
	}
	void Draw(void)
	{
		ColorCursor(m_Color);
		for(int y = 0; y < m_Height; y++)
		{
			for(int x = 0; x < m_Width; x++)
			{
				if(m_Map[y][x] != ' ')
				{
					MoveCursor(m_X+x, m_Y+y);
					printf("%c", BLOCK_CHAR);
				}
			}
		}
	}
	void Erase(void)
	{
		ColorCursor(DEFAULT_COLOR);
		for(int y = 0; y < m_Height; y++)
		{
			for(int x = 0; x < m_Width; x++)
			{
				if(m_Map[y][x] != ' ')
				{
					MoveCursor(m_X+x, m_Y+y);
					printf("%c", BLOCK_CHAR);
				}
			}
		}
	}
};

struct Game
{
	bool m_GameRunning;
	ActionTimer m_Timer;
	Conglomerate m_Piece;
	Conglomerate m_NextPiece;
	WellBlock** m_Well;
	int m_Score;

	Game():m_GameRunning(true)
	{
		m_Score = 0;
		m_Well = new WellBlock* [WELL_HEIGHT];
		for(int y = 0; y < WELL_HEIGHT; ++y)
			m_Well[y] = new WellBlock[WELL_WIDTH];
	}
	~Game()
	{
		if(m_Well)
		{
			for(int y = 0; y < WELL_HEIGHT; ++y)
				delete [] m_Well[y];
			delete [] m_Well;
			m_Well = 0;
		}
	}

	void SwapPieces(void)
	{
		m_Piece.Copy(m_NextPiece);
		m_NextPiece.CreatePiece();
	}

	void Input(int Input)
	{
		m_Piece.Erase();
		int oldY = m_Piece.m_Y;
		int oldX = m_Piece.m_X;
		int rotD = 0;//rotation direction
		switch(Input)
		{
		case 'a':	--m_Piece.m_X;			break;
		case 's':	++m_Piece.m_Y;
			m_Timer.ResetTime();			break;
		case 'd':	++m_Piece.m_X;			break;
		case 'e':	m_Piece.RotateCW();		
			rotD = ROTATED_CW;		break;
		case 'q':	m_Piece.RotateCCW();	
			rotD = ROTATED_CCW;		break;
		case 27 :	m_GameRunning=false;	break;
		default:							break;
		}
		if(m_Piece.m_Y+m_Piece.m_Height > WELL_HEIGHT || m_Piece.m_X < 0 || m_Piece.m_X+m_Piece.m_Width > WELL_WIDTH || IsClipped() == true)
		{
			switch(rotD)
			{
			case ROTATED_CW:
				m_Piece.RotateCCW();	break;
			case ROTATED_CCW:
				m_Piece.RotateCW();		break;
			default:
				m_Piece.m_Y = oldY;
				m_Piece.m_X = oldX;		break;
			}
		}
		m_Piece.Draw();
	}
	bool IsClipped(void)
	{
		for(int y = 0; y < m_Piece.m_Height; ++y)
			for(int x = 0; x < m_Piece.m_Width; ++x)
				if(m_Well[m_Piece.m_Y+y][m_Piece.m_X+x].m_State==1 && m_Piece.m_Map[y][x]!=' ')
					return true;
		return false;
	}

	bool CheckSolidify(void)
	{
		for(int y = 0; y < m_Piece.m_Height; ++y)
			for(int x = 0; x < m_Piece.m_Width; ++x)
				if(m_Piece.m_Y+y == WELL_HEIGHT-1 || (m_Well[m_Piece.m_Y+y+1][m_Piece.m_X+x].m_State==1 && m_Piece.m_Map[y][x]!=' '))
					return true;
		return false;
	}

	void Solidify(void)
	{
		for(int y = 0; y < m_Piece.m_Height; ++y)
			for(int x = 0; x < m_Piece.m_Width; ++x)
			{
				if(m_Piece.m_Map[y][x] != ' ')
				{
					m_Well[m_Piece.m_Y+y][m_Piece.m_X+x].m_State = 1;
					m_Well[m_Piece.m_Y+y][m_Piece.m_X+x].m_Color = m_Piece.m_Color;
				}
			}
	}
	void CheckRows(void)
	{
		bool RowFull = true;
		int RowNumber = -1;
		for(int y = 0; y < WELL_HEIGHT; ++y)
		{
			for(int x = 0; x < WELL_WIDTH; ++x)
			{
				RowNumber = y;
				if(m_Well[y][x].m_State == 0)
					RowFull = false;
			}
			if(RowFull == true)
			{
				m_Score += 100;
				for(int x = 0; x < WELL_WIDTH; ++x)
				{
					m_Well[RowNumber][x].m_State = 0;
					m_Well[RowNumber][x].m_Color = DEFAULT_COLOR;
				}
				for(int dy = RowNumber; dy > 0; --dy)
				{
					for(int dx = 0; dx < WELL_WIDTH; ++dx)
					{
						m_Well[dy][dx] = m_Well[dy-1][dx];
					}
				}
			}
			RowNumber = -1;
			RowFull = true;
		}
	}
	void DrawWellComplete(void)
	{
		for(int y = 0; y < WELL_HEIGHT; ++y)
		{
			for(int x = 0; x < WELL_WIDTH; ++x)
			{
				MoveCursor(x, y);
				if(m_Well[y][x].m_Color != DEFAULT_COLOR)
					ColorCursor(m_Well[y][x].m_Color);
				else
					ColorCursor(DEFAULT_COLOR);
				putchar(BLOCK_CHAR);
			}
		}
		PrintScore();
	}
	void PrintScore()
	{
		ColorCursor(FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE);
		MoveCursor(11, 0);
		printf("Score = %12d", m_Score);
	}
	void DrawNextPiece(void)
	{
		MoveCursor(11, 4);
		ColorCursor(FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE);
		printf("Next Piece");
		for(int y = 5; y < 9; y++)
		{
			for(int x = 11; x < 15; ++x)
			{
				MoveCursor(x, y);
				putchar(' ');
			}
		}
		ColorCursor(m_NextPiece.m_Color);
		for(int y = 0; y < m_NextPiece.m_Height; ++y)
		{
			for(int x = 0; x < m_NextPiece.m_Width; ++x)
			{
				if(m_NextPiece.m_Map[y][x]!=' ')
				{
					MoveCursor(11+x, 5+y);
					printf("%c", BLOCK_CHAR);
				}
			}
		}
	}
};

int main(int argc, char** argv)
{
	HideCursor();
	srand(unsigned(time(0)));
	Game g;
	g.DrawWellComplete();

	g.m_Piece.CreatePiece();
	g.m_NextPiece.CreatePiece();

	g.m_Piece.Draw();
	g.DrawNextPiece();

	while(g.m_GameRunning)
	{
		g.m_Timer.LogTime();
		if(_kbhit())
		{
			g.Input(_getch());
		}
		if(g.m_Timer.IsTime(1000))//Time for gravity to take affect
		{
			g.Input('s');
			if(g.CheckSolidify())
			{
				g.Solidify();
				g.CheckRows();
				g.DrawWellComplete();
				g.SwapPieces();
				if(g.IsClipped())
				{
					g.m_GameRunning = false;
				}
				g.m_Piece.Draw();
				g.DrawNextPiece();
			}
		}
	}

	MoveCursor(11,19);
	ColorCursor(FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE);
	printf("Thank You For Playing!");
	_getch();
	return 0;
}